/*
 * @(#)InetAddress.java	1.47 2001/12/12
 *
 * 版权所有2002 Sun Microsystems公司版权所有。太阳专有/保密。使用受许可条款的约束。
 */

package java.net;

import java.util.Hashtable;

/**
 * This class represents an Internet Protocol (IP) address. 
 * <p>
 * 应用程序应该使用方法 getLocalHost ，getByName 或 getAllByName 来创建一个新的 InetAddress 实例。
 *
 * @author  Chris Warth
 * @version 1.47, 12/12/01
 * @see     InetAddress#getAllByName(String)
 * @see     InetAddress#getByName(String)
 * @see     InetAddress#getLocalHost()
 * @since   JDK1.0
 */
public final 
class InetAddress implements java.io.Serializable {
    String hostName;
    int address;    // 目前我们只有效地处理32位地址。然而，这个字段可以扩展为一个字节数组或一个64位的数量，而不需要太多的工作。
    int family;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = 3286316764910316507L;

    /*
     * Load net library into runtime.
     */
    static {
	System.loadLibrary("net");
    }

    /** 
     * Socket.accept()方法的构造函数。这将创建一个空的InetAddress，由accept()方法填充。
	 * 但是，这个InetAddress不会放在地址缓存中，因为它不是按名称创建的。
     */
    InetAddress() {
      family = impl.getInetFamily();
    }

    /**
     * Creates an InetAddress with the specified host name and IP address.
     * @param hostName the specified host name
     * @param addr 指定的IP地址。该地址应按网络字节顺序排列。
     * @exception UnknownHostException If the address is unknown.
     */
    InetAddress(String hostName, byte addr[]) {
	this.hostName = new String(hostName);
	this.family = impl.getInetFamily();
	/*
	 * 在这里，我们必须小心地维护地址的网络字节顺序。当它传入时，地址的最高位字节位于addr[0]中。
	 * 实际上，它们在数组中的最终顺序并不重要，只要它是有文档记录的并且一致。
	 */
	address  = addr[3] & 0xFF;
	address |= ((addr[2] << 8) & 0xFF00);
	address |= ((addr[1] << 16) & 0xFF0000);
	address |= ((addr[0] << 24) & 0xFF000000);
    }

    /**
     * 检查InetAddress是否是IP组播地址的实用程序例程。IP组播地址为D类地址，即地址的前四位是1110。
     * @since   JDK1.1
     */
    public boolean isMulticastAddress() {
	return ((address & 0xf0000000) == 0xe0000000);
    }

    /**
     * 返回此地址的完全限定主机名。如果主机为空，则此地址指的是本地机器的任何可用网络地址。
     *
     * @return  the fully qualified host name for this address.
     * @since   JDK1.0
     */
    public String getHostName() {
	if (hostName == null) {
	    try {
		hostName = new String(impl.getHostByAddr(address));
		InetAddress[] arr = (InetAddress[])addressCache.get(hostName);
		if(arr != null) {
		    for(int i = 0; i < arr.length; i++) {
			if(hostName.equalsIgnoreCase(arr[i].hostName) && 
			   address != arr[i].address) {
			    /*
			     *这意味着有人在用DNS玩有趣的游戏，这个主机名曾经有一个IP地址，现在它有另一个。
			     * 无论如何，不要让他们“看到”新的hostName，也不要缓存它。
			     */
			    hostName = getHostAddress();
			    break;
			}
		    }
		} else { 
		    /*
		     * 主机名之前不在缓存中。这是一个真正的主机名，而不是“%d.%d.%d”.%d"，并进行缓存。
		     */
		    arr = new InetAddress[1];
		    arr[0] = this;
		    addressCache.put(hostName, arr);
		}
		/*
		 * 最后检查调用代码是否允许知道该IP地址的主机名，即连接到主机
		 */
		SecurityManager sec = System.getSecurityManager();
		if (sec != null && !sec.getInCheck()) {
		    sec.checkConnect(hostName, -1);
		}
	    } catch (SecurityException e) {
		hostName = getHostAddress();
	    } catch (UnknownHostException e) {
		hostName = getHostAddress();
	    }
	}
	return hostName;
    }

    /**
     * 返回<code>InetAddress</code>对象的原始IP地址。结果是网络字节顺序:地址的最高字节在<code>getAddress()[0]</code>中。
     *
     * @return  the raw IP address of this object.
     * @since   JDK1.0
     */
    public byte[] getAddress() {	
	byte[] addr = new byte[4];

	addr[0] = (byte) ((address >>> 24) & 0xFF);
	addr[1] = (byte) ((address >>> 16) & 0xFF);
	addr[2] = (byte) ((address >>> 8) & 0xFF);
	addr[3] = (byte) (address & 0xFF);
	return addr;
    }

    /**
     * Returns the IP address string "%d.%d.%d.%d"
     * @return raw IP address in a string format
     * @since   JDK1.1
     */
    public String getHostAddress() {	
         return ((address >>> 24) & 0xFF) + "." +
                ((address >>> 16) & 0xFF) + "." +
                ((address >>>  8) & 0xFF) + "." +
                ((address >>>  0) & 0xFF);
     }
 

    /**
     * Returns a hashcode for this IP address.
     *
     * @return  a hash code value for this IP address. 
     * @since   JDK1.0
     */
    public int hashCode() {
	return address;
    }

    /**
     * 将此对象与指定对象进行比较。
	 * 当且仅当参数不<code>null</code>并且它表示与该对象相同的IP地址时，结果为<code>true</code>。
     * <p>
     * <code>InetAddress</code>的两个实例表示相同的IP地址，
	 * 如果<code>getAddress</code>返回的字节数组的长度是相同的，并且字节数组的每个数组组件是相同的。
     *
     * @param   obj   the object to compare against.
     * @return  <code>true</code> if the objects are the same;
     *          <code>false</code> otherwise.
     * @see     InetAddress#getAddress()
     * @since   JDK1.0
     */
    public boolean equals(Object obj) {
	return (obj != null) && (obj instanceof InetAddress) &&
	    (((InetAddress)obj).address == address);
    }

    /**
     * Converts this IP address to a <code>String</code>.
     *
     * @return  a string representation of this IP address.
     * @since   JDK1.0
     */
    public String toString() {
	return getHostName() + "/" + getHostAddress();
    }

    /* 
     * Cached addresses - our own litle nis, not! 
     *
     * 请勿清除缓存中的数字IP地址，因为重复的动态DNS名称查找会使系统容易受到主机名欺骗攻击。
     * 在DNS中查找主机名并将其输入Java缓存后，从那时起，主机名将仅通过缓存转换为IP地址。
     */
    static Hashtable	    addressCache = new Hashtable();
    static InetAddress	    unknownAddress;
    static InetAddress	    anyLocalAddress;
    static InetAddress      localHost;
    static InetAddress[]    unknown_array; // put THIS in cache
    static InetAddressImpl  impl;

    /* 
     * generic localHost to give back to applets 
     * - private so not API delta
     */
    private static InetAddress      loopbackHost;

    static {

	/*
	 * 属性“impl.prefix"将被加在我们实例化的实现对象的类名前，我们将实际工作委托给它(如本机方法)。
	 * 此属性可以在java的不同实现中有所不同 java.* 类，默认为空字符串“”。
	 */

	String prefix = System.getProperty("impl.prefix", "");
	try {
	    impl = null;
	    impl = (InetAddressImpl)(Class.forName("java.net." + prefix + "InetAddressImpl")
						   .newInstance());
	} catch (ClassNotFoundException e) {
	    System.err.println("Class not found: java.net." + prefix + 
			       "InetAddressImpl:\ncheck impl.prefix property " +
			       "in your properties file.");
	} catch (InstantiationException e) {
	    System.err.println("Could not instantiate: java.net." + prefix + 
			       "InetAddressImpl:\ncheck impl.prefix property " +
			       "in your properties file.");
	} catch (IllegalAccessException e) {
	    System.err.println("Cannot access class: java.net." + prefix + 
			       "InetAddressImpl:\ncheck impl.prefix property " +
			       "in your properties file.");
	}

	if (impl == null) {
	    try {
		impl = (InetAddressImpl)(Class.forName("java.net.InetAddressImpl")
					 .newInstance());
	    } catch (Exception e) {
		throw new Error("System property impl.prefix incorrect");
	    }
	}

	unknownAddress = new InetAddress();
	anyLocalAddress = new InetAddress();
	impl.makeAnyLocalAddress(anyLocalAddress);
	byte[] IP = new byte[4];
	IP[0] = 0x7F;
	IP[1] = 0x00;
	IP[2] = 0x00;
	IP[3] = 0x01;
	loopbackHost = new InetAddress("localhost", IP);

	/* find the local host name */
	try {
	    localHost = new InetAddress();
	    localHost.hostName = impl.getLocalHostName();
	    /*
	     * 我们显式地保留未初始化的本地主机地址。
	     * 静态初始化器中的DNS查找将导致与网络断开连接的机器挂起它将试图查询不存在的DNS服务器。
	     *
	     * 相反，我们只获得本地主机的主机名。本机代码只调用gethostname()，这应该是非常无害的它不应该尝试联系DNS服务器。
	     * 如果任何应用程序调用InetAddress.getLocalHost()，则初始化本地主机地址(如果尚未初始化)。
	     *
	     * 请注意，要实现这一点，还必须localHost InetAddress被_NOT_放入静态初始化器中的地址缓存中
	     * (如果从静态初始化器调用getByName()就会发生这种情况)。在初始化后将它放在addressCache中是OK的。
	     *
	     * localHost地址的统一状态是-1，或者IP地址255.255.255.255，我们知道这不是一个合法的主机地址。
	     */
	    localHost.address = -1;
	} catch (Exception ex) { /* this shouldn't happen */
	    localHost = unknownAddress;
	}

	/* cache the name/address pair "0.0.0.0"/0.0.0.0 */
	String unknownByAddr = new String("0.0.0.0");
	unknown_array = new InetAddress[1];
	unknown_array[0] = new InetAddress(unknownByAddr, unknownAddress.getAddress());
	addressCache.put(unknownByAddr, unknown_array);
    }

    /**
     * 给定主机名，确定主机的IP地址。主机名可以是机器名，如“<code>java.sun.com</code>”，
	 * 也可以是表示其IP地址的字符串，如“<code>206.26.48.100</code>”。
     *
     * @param      host   the specified host, or <code>null</code> for the
     *                    local host.
     * @return     an IP address for the given host name.
     * @exception  UnknownHostException  if no IP address for the
     *               <code>host</code> could be found.
     * @since      JDK1.0
     */
    public static InetAddress getByName(String host)
	throws UnknownHostException {
	Object obj = null;
	if (host == null || host.length() == 0) {
	    return loopbackHost;
	}

        if (!Character.isDigit(host.charAt(0))) {
	    return getAllByName0(host)[0];
	} else {
	    /*
	     * 字符串(可能)表示一个数字IP地址。将其解析为int型，不要做不必要的反向查找，让hostName为空，不要缓存。
	     * 如果它不是IP地址(即不是“%d %d %d %d %d”)，或者任何元素> 0xFF，我们将其视为主机名，并以这种方式进行查找。
	     * 这似乎完全符合RFC1123规范:像3com这样的部分主机名。Domain4在技术上是有效的。
	     */

	    int IP = 0x00;
	    int hitDots = 0;
	    char[] data = host.toCharArray();

	    for(int i = 0; i < data.length; i++) {
		char c = data[i];
		if (c < 48 || c > 57) { // !digit
		    return getAllByName0(host)[0];
		}
		int b = 0x00;
		while(c != '.') {
		    if (c < 48 || c > 57) { // !digit
			return getAllByName0(host)[0];
		    }
		    b = b*10 + c - '0';

		    if (++i >= data.length)
			break;
		    c = data[i];
		}
		if(b > 0xFF) { /* bogus - bigger than a byte */
		    return getAllByName0(host)[0];
		}
		IP = (IP << 8) + b;
		hitDots++;
	    }

	    if(hitDots != 4 || host.endsWith(".")) {
		return getAllByName0(host)[0];
	    }

	    InetAddress in = new InetAddress();
	    in.address = IP;
	    in.hostName = null;
	    return in;
	}

    }

    /** 
     * 给定主机名，确定主机的所有IP地址。主机名可以是机器名，如“<code>java.sun.com</code>”，
	 * 也可以是表示其IP地址的字符串，如“<code>206.26.48.100</code>”。
     *
     * @param      host   the name of the host.
     * @return     an array of all the IP addresses for a given host name.
     * @exception  UnknownHostException  if no IP address for the
     *               <code>host</code> could be found.
     * @since      JDK1.0
     */
    public static InetAddress getAllByName(String host)[]
	throws UnknownHostException {

	if (host == null || host.length() == 0) {
	    throw new UnknownHostException("empty string");
	}

	if(Character.isDigit(host.charAt(0))) {
	    InetAddress[] ret = new InetAddress[1];
	    ret[0] = getByName(host);
	    return ret;
	} else {
	    return getAllByName0(host);
	}
    }

    private static InetAddress[] getAllByName0 (String host) 
	throws UnknownHostException  {
	/* If it gets here it is presumed to be a hostname */
	/* Cache.get can return: null, unknownAddress, or InetAddress[] */
        Object obj = null;
	Object objcopy = null;

	/* make sure the connection to the host is allowed, before we
	 * give out a hostname
	 */
	SecurityManager security = System.getSecurityManager();
	if (security != null && !security.getInCheck()) {
	    security.checkConnect(host, -1);
	}

	synchronized (addressCache) {
	    obj = addressCache.get(host);

	/* If no entry in cache, then do the host lookup */
	
	    if (obj == null) {
		try {
		    /*
		     * 不要在构造函数中调用lookup()。如果您这样做，当查找失败时，您仍然在分配空间。
		     */
		    byte[][] byte_array = impl.lookupAllHostAddr(host);
		    InetAddress[] addr_array = new InetAddress[byte_array.length];

		    for (int i = 0; i < byte_array.length; i++) {
			byte addr[] = byte_array[i];
			addr_array[i] = new InetAddress(host, addr);
		    }
		    obj = addr_array;
		} catch (UnknownHostException e) {
		    obj  = unknown_array;
		}
		if (obj != unknown_array)
		    addressCache.put(host, obj);
	    }
	} /* end synchronized block */

	if (obj == unknown_array) {
	    /*
	     * We currently cache the fact that a host is unknown.
	     */
	    throw new UnknownHostException(host);
	}

	/* Make a copy of the InetAddress array */
	try {
	      objcopy = ((InetAddress [])obj).clone();
	      // 下面的代码行是一个hack，以确保代码可以为损坏的编译器和修复的编译器编译。
	      if (objcopy == null) 
		  throw new CloneNotSupportedException();
	} catch (CloneNotSupportedException cnse) {
	      cnse.printStackTrace();
	}

	return (InetAddress [])objcopy;
    }

    /**
     * Returns the local host.
     *
     * @return     the IP address of the local host.
     * @exception  UnknownHostException  if no IP address for the
     *               <code>host</code> could be found.
     * @since      JDK1.0
     */
    public static InetAddress getLocalHost() throws UnknownHostException {
        if (localHost.equals(unknownAddress)) {
	    throw new UnknownHostException();
	}

	try {
	    /*
	     * 如果本地主机的地址尚未初始化，请对其进行初始化。
	     * 它不再在静态初始值设定项中初始化（请参阅那里的注释）。
	     */

	    if (localHost.address == -1) {
		localHost = getAllByName(localHost.hostName)[0];
		/* This puts it in the address cache as well */
	    }

         /*
         * 确保连接到主机是允许的:如果是，返回“真实的”localHost;如果不是，返回环回"127.0.0.1"
	     */
	    SecurityManager security = System.getSecurityManager();
	    if (security != null && !security.getInCheck()) 
		security.checkConnect(localHost.getHostName(), -1);
	    return localHost;

	} catch (SecurityException e) {
	    return loopbackHost;
	}
    }
}

class InetAddressImpl {
    native String getLocalHostName() throws UnknownHostException;
    native void makeAnyLocalAddress(InetAddress addr);
    native byte[][]
        lookupAllHostAddr(String hostname) throws UnknownHostException;
    native String getHostByAddr(int addr) throws UnknownHostException;
    native int getInetFamily();
}
